package com.ruoyi.fjtseac.util;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.oss.*;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.mts.model.v20140618.SubmitJobsRequest;
import com.aliyuncs.mts.model.v20140618.SubmitJobsResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.google.gson.Gson;
import com.ruoyi.common.core.redis.RedisCache;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.spring.web.json.Json;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * @author zzl
 * @date 2022/7/7 17:06
 */
public class OSSUtil extends FileVerification implements FileUtil{

    @Value("${path.directory.real}")
    private String dir;

    //oss连接配置
    @Value("${oss.host}")
    private String host;
    @Value("${oss.bucketName}")
    private String bucketName;
    @Value("${oss.endpoint}")
    private String endpoint;
    @Value("${oss.accessKeyId}")
    private String accessKeyId;
    @Value("${oss.accessKeySecret}")
    private String accessKeySecret;
    @Value("${oss.mpsRegionId}")
    private String mpsRegionId;
    @Value("${oss.pipelineId}")
    private String pipelineId;
    @Value("${oss.templateId}")
    private String templateId;
    @Value("${oss.ossLocation}")
    private String ossLocation;
    @Value("${oss.roleArn}")
    private String roleArn;
    @Value("${oss.roleSessionName}")
    private String roleSessionName;
    @Autowired
    RedisCache redisCache;

    /**
     * ACL为私有时，使用该方法上传文件
     * 上传文件到oss服务器
     * @param file 目标文件
     * @param path 取值为：video或video-temp（转码前的存储位置）、image、audio，用于指定具体存入的文件夹（区别不同类型的文件）
     * @return "false"由于上传需要返回url存入数据库，故以“false”为失败的标志
     */
    @Override
    public String upload(MultipartFile file, String path, String uuid) {
        //判断文件是否为空
        if (file == null || file.isEmpty()) {
            return FALSE;
        }
        //通过文件后缀过滤文件
        if (!extensionFilter(file, path)) {
            return FALSE;
        }
        // 获取文件名
        String fileName = file.getOriginalFilename();
        fileName = UUID.randomUUID() + "_" + fileName;
        //最终路径
        String endPath = dir + "/" + path + "/" + fileName;
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        //当图片为jpg时，上传文件的http头的类型为image/jpeg导致访问时变为下载，在此处修改其类型。
        if ("jpg".equals(fileName.substring(fileName.lastIndexOf(".") + 1))) {
            objectMetadata.setContentType("image/jpg");
        }
        try {
            //上传文件
            ossClient.putObject(bucketName, endPath, file.getInputStream(), objectMetadata);
        } catch (IOException e) {
            return FALSE;
        }finally {
            ossClient.shutdown();
        }
        //ACL为私有时，返回使用下面语句
//        return endPath;
        //ACL为公有读时，使用一下语句返回
        return host + "/" + endPath;
    }

    /**
     * 通过path删除oss上的文件，多个文件删除时，全部删除成功才返回true，否则返回false;
     *
     * @param endPath
     */
    @Override
    public boolean delete(String... endPath) {
        boolean isAllSuccess = true;
        OSS ossClient = null;
        for (String simpleEndPath : endPath) {
            // 创建OSSClient实例。
            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            try {
                if (exists(simpleEndPath)){
                    ossClient.deleteObject(bucketName, simpleEndPath);
                }
            } catch (Exception e) {
                e.printStackTrace();
                isAllSuccess = false;
            }
        }
        // 关闭OSSClient。
        if (ossClient != null) {
            ossClient.shutdown();
        }
        return isAllSuccess;
    }


    /**
     * 判断文件是否存在
     * @param path
     * @return
     */
    @Override
    public boolean exists(String path) {
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        try {
            boolean exist = ossClient.doesObjectExist(bucketName, path);
            ossClient.shutdown();
            if (exist) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            throw new ServiceException("文件不存在");
        }
    }

    /**
     * 将原来oss中的视频文件转为可在浏览器播放的H.264编码
     * @param inputUrl 输入文件的文件路径（包含从bucket及其后的文件路径和文件名+后缀）
     * @param outputPath 转码后的文件路径 （包含从bucket及其后的文件路径和文件名+后缀）
     * @return true 测试时发现，VideoServiceImpl的upload接口中虽然删除原视频的代码在转码后，但原视频却会在转码前被删除，这里怀疑是多线程在作怪，使用返回值充当锁来控制视频的删除。
     */
    @Override
    public void transcodeToH264(String inputUrl, String outputPath) {
        // 创建DefaultAcsClient实例并初始化
        DefaultProfile profile = DefaultProfile.getProfile(
                mpsRegionId,      // 地域ID
                accessKeyId,      // RAM账号的AccessKey ID
                accessKeySecret); // RAM账号Access Key Secret
        IAcsClient client = new DefaultAcsClient(profile);
        // 创建API请求并设置参数
        SubmitJobsRequest request = new SubmitJobsRequest();
        // Input
        JSONObject input = new JSONObject();
        input.put("Location", ossLocation);
        input.put("Bucket", bucketName);
        try {
            input.put("Object", URLEncoder.encode(inputUrl, "utf-8").replace("+", "%20"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("input URL encode failed");
        }
        request.setInput(input.toJSONString());
        // Output
        String outputOSSObject;
        try {
            outputOSSObject = URLEncoder.encode(outputPath, "utf-8").replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("output URL encode failed");
        }
        JSONObject output = new JSONObject();
        output.put("OutputObject", outputOSSObject);
//            // Ouput->Container
//            JSONObject container = new JSONObject();
//            container.put("Format", "mp4");
//            output.put("Container", container.toJSONString());
//            // Ouput->Video  设置了模板id，若下面配置则模板失效
//            JSONObject video = new JSONObject();
//            video.put("Codec", "H.264");
//            video.put("Bitrate", "1500");
//            video.put("Width", "1280");
//            video.put("Fps", "25");
//            output.put("Video", video.toJSONString());
//            // Ouput->Audio
//            JSONObject audio = new JSONObject();
//            audio.put("Codec", "AAC");
//            audio.put("Bitrate", "128");
//            audio.put("Channels", "2");
//            audio.put("Samplerate", "44100");
//            output.put("Audio", audio.toJSONString());
        // Ouput->TemplateId
        output.put("TemplateId", templateId);
        JSONArray outputs = new JSONArray();
        outputs.add(output);
        request.setOutputs(outputs.toJSONString());
        request.setOutputBucket(bucketName);
        request.setOutputLocation(ossLocation);
        // PipelineId
        request.setPipelineId(pipelineId);
        // 发起请求并处理应答或异常
        SubmitJobsResponse response;
        try {
            response = client.getAcsResponse(request);
            System.out.println("RequestId is:" + response.getRequestId());
            if (response.getJobResultList().get(0).getSuccess()) {
                System.out.println("JobId is:" + response.getJobResultList().get(0).getJob().getJobId());
            } else {
                System.out.println("SubmitJobs Failed code:" + response.getJobResultList().get(0).getCode() +
                        " message:" + response.getJobResultList().get(0).getMessage());
            }
        } catch (ClientException | com.aliyuncs.exceptions.ClientException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object getProgress(String uuid) {
        if (uuid == null) {
            return null;
        }
        return redisCache.getCacheObject(uuid);
    }

    /**
     * ACL为私有时，使用这个来获取临时访问的文件url
     * 通过存储对象名批量获取oss上完整的路径
     * @param objectNames 存储对象名，即数据库中的url
     * @return
     */
    public List<String> getObjectsUrl(String... objectNames) {

        if (objectNames == null || objectNames.length == 0) {
            return null;
        }
        // 从STS服务获取的安全令牌（SecurityToken）。
        JSONObject sts = getOssSTS();
        String securityToken = sts.getString("securityToken");
        String accessKeyId = sts.getString("accessKeyId");
        String accessKeySecret = sts.getString("accessKeySecret");
        // 从STS服务获取临时访问凭证后，您可以通过临时访问密钥和安全令牌生成OSSClient。
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, securityToken);
        List<String> urlList = new ArrayList<>();
        try {
            // 设置签名URL过期时间，单位为毫秒。
            Date expiration = new Date(new Date().getTime() * 3600 * 1000);

            for (String objectName : objectNames) {
                String url = ossClient.generatePresignedUrl(bucketName, objectName, expiration).toString();
                urlList.add(url);
            }
        } catch (OSSException oe) {
            throw  new OSSException("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason."
            +"\n" + "Error Message:" + oe.getErrorMessage()
            + "\n" +"Error Code:" + oe.getErrorCode()
            +"\n" + "Request ID:" + oe.getRequestId()
            +"\n" + "Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            throw new ClientException("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network."+ "\n"+ "Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return urlList;
    }

    /**
     * ACL为私有时，使用这个通过STS来调用
     * 适应oss的sts获取临时凭证以及密钥
     * @return
     */
    public JSONObject getOssSTS() {
        //构建一个阿里云客户端，用于发起请求。
        //设置调用者（RAM用户或RAM角色）的AccessKey ID和AccessKey Secret。
        DefaultProfile profile = DefaultProfile.getProfile(mpsRegionId, accessKeyId, accessKeySecret);
        IAcsClient client = new DefaultAcsClient(profile);

        //构造请求，设置参数。关于参数含义和设置方法，请参见《API参考》。
        AssumeRoleRequest request = new AssumeRoleRequest();
        request.setSysRegionId(mpsRegionId);
        request.setRoleArn(roleArn);
        request.setRoleSessionName(roleSessionName);
        //发起请求，并得到响应。
        JSONObject json = new JSONObject();
        try {
            AssumeRoleResponse response = client.getAcsResponse(request);
            AssumeRoleResponse.Credentials credentials = response.getCredentials();
            json.put("securityToken", credentials.getSecurityToken());
            json.put("accessKeyId", credentials.getAccessKeyId());
            json.put("accessKeySecret", credentials.getAccessKeySecret());
        } catch (com.aliyuncs.exceptions.ClientException e) {
            e.printStackTrace();
        }
        return json;
    }
}
